/*******************************************************************************
 * Copyright (c) 2009 Remy Chi Jian Suen and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Remy Chi Jian Suen <remy.suen@gmail.com> - initial API and implementation
 ******************************************************************************/
package org.eclipse.ecf.internal.sync.resources.core;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.util.ECFException;
import org.eclipse.ecf.datashare.AbstractShare;
import org.eclipse.ecf.datashare.IChannelContainerAdapter;
import org.eclipse.ecf.sync.IModelChange;
import org.eclipse.ecf.sync.IModelChangeMessage;

public class ResourcesShare extends AbstractShare {

	private Boolean response;

	private Set sharedProjects = new HashSet();

	private ID receiverID;

	private ID containerID;

	private ID localID;

	public ResourcesShare(ID containerID, IChannelContainerAdapter adapter)
			throws ECFException {
		super(adapter);
		this.containerID = containerID;
	}

	private void attachListener() {
		if (sharedProjects.size() == 1) {
			SyncResourcesCore.getDefault().attachListener();
		}
	}

	private void detachListener() {
		if (sharedProjects.isEmpty()) {
			SyncResourcesCore.getDefault().detachListener();
		}
	}

	public void sendResponse(boolean accept, String projectName) {
		try {
			if (accept) {
				send(receiverID, new AcceptMessage(projectName));
			} else {
				send(receiverID, new DenyMessage(projectName));
			}
		} catch (ECFException e) {
			// TODO handle this
			e.printStackTrace();
		}
	}

	public Boolean getResponse() {
		if (response == null) {
			return null;
		}
		Boolean temp = response;
		response = null;
		return temp;
	}

	public ID getContainerID() {
		return containerID;
	}

	public ID getReceiverID() {
		return receiverID;
	}

	public ID getLocalID() {
		return localID;
	}

	public boolean isSharing(String projectName) {
		synchronized (sharedProjects) {
			return sharedProjects.contains(projectName);
		}
	}

	public void startShare(ID fromId, ID toID, String projectName)
			throws ECFException {
		if (sharedProjects.add(projectName)) {
			// reset in case we have a stale one
			response = null;
			
			try {
				send(toID, new StartMessage(projectName, fromId, toID));
				localID = fromId;
				receiverID = toID;
				attachListener();
			} catch (ECFException e) {
				receiverID = null;
				sharedProjects.remove(projectName);
				detachListener();
				throw e;
			}
		}
	}

	public void stopSharing(String projectName) {
		if (sharedProjects.remove(projectName)) {
			try {
				send(receiverID, new StopMessage(projectName));
			} catch (ECFException e) {
				// TODO handle this
				e.printStackTrace();
			} finally {
				receiverID = null;
			}

			detachListener();
		}
	}

	private void send(ID toID, Message message) throws ECFException {
		sendMessage(toID, message.serialize());
	}

	void send(byte[] bytes) throws ECFException {
		sendMessage(receiverID, bytes);
	}

	void sendResourceChangeMessage(IResource resource, int kind) {
		try {
			IModelChange change = ResourceChangeMessage.createResourceChange(
					resource, kind);
			IModelChangeMessage[] messages = ResourcesSynchronizationStrategy
					.getInstance().registerLocalChange(change);
			for (int i = 0; i < messages.length; i++) {
				send(messages[i].serialize());
			}
		} catch (ECFException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	protected void handleStartMessage(StartMessage msg) {
		receiverID = msg.getFromId();
		localID = msg.getLocalId();
		sharedProjects.add(msg.getProjectName());
		attachListener();
	}

	private void handleStopMessage(StopMessage msg) {
		sharedProjects.remove(msg.getProjectName());
		detachListener();
	}

	private void handleResourceChangeMessage(byte[] data) throws Exception {
		IModelChange remoteChange = ResourcesSynchronizationStrategy
				.getInstance().deserializeRemoteChange(data);
		final IModelChange[] remoteChanges = ResourcesSynchronizationStrategy
				.getInstance().transformRemoteChange(remoteChange);

		// create a scheduling rule to lock the projects
		ISchedulingRule[] rules = new ISchedulingRule[sharedProjects.size()];
		int index = 0;
		for (Iterator it = sharedProjects.iterator(); it.hasNext();) {
			String projectName = (String) it.next();

			rules[index] = ResourcesPlugin.getWorkspace().getRoot().getProject(
					projectName);
			index++;
		}

		try {
			// lock to prevent resource changes from being propagated
			lock(remoteChanges);
			applyRemoteChanges(remoteChanges, new MultiRule(rules));
		} finally {
			// unlock now that we've applied the remote changes to our
			// own workspace
			unlock(remoteChanges);
		}

		if (remoteChange instanceof BatchModelChange) {
			BatchModelChange batchChange = (BatchModelChange) remoteChange;
			batchChange.setOutgoing(false);
			batchChange.setTime(System.currentTimeMillis());

			SyncResourcesCore.add(batchChange);
		}
	}

	protected void lock(IModelChange[] remoteChanges) {
		SyncResourcesCore.lock();
	}

	protected void unlock(IModelChange[] remoteChanges) {
		SyncResourcesCore.unlock();
	}

	private void applyRemoteChanges(final IModelChange[] remoteChanges,
			ISchedulingRule rule) throws CoreException {
		ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
			public void run(IProgressMonitor monitor) throws CoreException {
				monitor.beginTask("Processing remote changes...",
						remoteChanges.length);
				for (int i = 0; i < remoteChanges.length; i++) {
					if (monitor.isCanceled()) {
						return;
					}
					// applies the resource changes
					remoteChanges[i].applyToModel(containerID);
					monitor.worked(1);
				}
				monitor.done();
			}
		}, rule, IWorkspace.AVOID_UPDATE, null);

	}

	protected void handleMessage(ID fromContainerID, byte[] data) {
		try {
			Object message = Message.deserialize(data);
			if (message instanceof StartMessage) {
				handleStartMessage((StartMessage) message);
			} else if (message instanceof StopMessage) {
				handleStopMessage((StopMessage) message);
			} else if (message instanceof AcceptMessage) {
				response = Boolean.TRUE;
			} else if (message instanceof DenyMessage) {
				sharedProjects.remove(((DenyMessage) message).getProjectName());
				receiverID = null;
				detachListener();
				response = Boolean.FALSE;
			} else {
				handleResourceChangeMessage(data);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
